from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from app.extensions import db
from datetime import datetime


roles_permissions = db.Table('roles_permissions',
                             db.Column('role_id', db.Integer, db.ForeignKey('role.id')),
                             db.Column('permission_id', db.Integer, db.ForeignKey('permission.id'))
                             )


class Permission(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(30), unique=True)
    roles = db.relationship('Role', secondary=roles_permissions, back_populates='permissions')


class Role(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(30), unique=True)
    users = db.relationship('User', back_populates='role')
    permissions = db.relationship('Permission', secondary=roles_permissions, back_populates='roles')

    @staticmethod
    def init_role():
        roles_permissions_map = {
            'User': ['SUBSCRIBE'],
            'Pro': ['SUBSCRIBE'],
            'Administrator': ['SUBSCRIBE', 'MANAGE']
        }

        for role_name in roles_permissions_map:
            role = Role.query.filter_by(name=role_name).first()
            if role is None:
                role = Role(name=role_name)
                db.session.add(role)
            role.permissions = []
            for permission_name in roles_permissions_map[role_name]:
                permission = Permission.query.filter_by(name=permission_name).first()
                if permission is None:
                    permission = Permission(name=permission_name)
                    db.session.add(permission)
                role.permissions.append(permission)
        db.session.commit()


class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(20), index=True)
    email = db.Column(db.String(128), unique=True)

    role_id = db.Column(db.Integer, db.ForeignKey('role.id'))
    role = db.relationship('Role', back_populates='users')

    password_hash = db.Column(db.String(128))
    subscription = db.relationship('Subscribe', back_populates='subscriber', cascade='all')
    timestamp = db.Column(db.DateTime, default=datetime.utcnow, index=True)

    def __init__(self, **kwargs):
        super(User, self).__init__(**kwargs)
        self.set_role()

    def set_password(self, password):
        self.password_hash = generate_password_hash(password)

    def validate_password(self, password):
        return check_password_hash(self.password_hash, password)

    def subscribe(self, crawler):
        if not self.is_subscribing(crawler):
            subscribe = Subscribe(subscriber=self, subscribed=crawler)
            db.session.add(subscribe)
            db.session.commit()

    def unsubscribe(self, crawler):
        subscribe = Subscribe.query.with_parent(self).filter_by(crawler_id=crawler.id).first()
        if subscribe:
            db.session.delete(subscribe)
            db.session.commit()

    def is_subscribing(self, crawler):
        return Subscribe.query.with_parent(self).filter_by(crawler_id=crawler.id).first() is not None

    def set_role(self):
        self.role = Role.query.filter_by(name='User').first()
        db.session.commit()

    @property
    def is_admin(self):
        return self.role.name == 'Administrator'

    def can(self, permission_name):
        permission = Permission.query.filter_by(name=permission_name).first()
        return permission is not None and self.role is not None and permission in self.role.permissions


class Subscribe(db.Model):
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
    crawler_id = db.Column(db.Integer, db.ForeignKey('crawler.id'), primary_key=True)
    subscriber = db.relationship('User', back_populates='subscription', lazy='joined')
    subscribed = db.relationship('Crawler', back_populates='subscribers', lazy='joined')
    timestamp = db.Column(db.DateTime, default=datetime.utcnow)


tagging = db.Table('tagging',
                   db.Column('crawler_id', db.Integer, db.ForeignKey('crawler.id')),
                   db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'))
                   )


class Crawler(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(128))

    category_id = db.Column(db.Integer, db.ForeignKey('category.id'))
    category = db.relationship('Category', back_populates='crawlers')

    rate = db.Column(db.Integer, index=True, default=86400)
    type = db.Column(db.Integer)     # 0为RSS， 1为爬虫1号, 2为特殊处理
    source = db.Column(db.String(64))

    tags = db.relationship('Tag', secondary=tagging, back_populates='crawlers')
    slug = db.Column(db.String(64))     # 用于猜你喜欢，同一个slug对应一个家族，比如zhihu

    records = db.relationship('Record', back_populates='crawler')
    describe = db.Column(db.String(255))
    image = db.Column(db.String(64))
    subscribers = db.relationship('Subscribe', back_populates='subscribed', cascade='all')
    update = db.Column(db.DateTime, default=datetime.utcnow)


class Tag(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), index=True, unique=True)
    crawlers = db.relationship('Crawler', secondary=tagging, back_populates='tags')


class Category(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(128))
    crawlers = db.relationship('Crawler', back_populates='category')


class Record(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(128))
    url = db.Column(db.String(2048))

    crawler_id = db.Column(db.Integer, db.ForeignKey('crawler.id'))
    crawler = db.relationship('Crawler', back_populates='records')

    heat = db.Column(db.String(64))
    timestamp = db.Column(db.DateTime, default=datetime.utcnow, index=True)


class Suggestion(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    website = db.Column(db.String(128))
    describe = db.Column(db.String(255))
    email = db.Column(db.String(128))
    count = db.Column(db.Integer, default=1)
    is_finish = db.Column(db.Boolean, default=False)
    timestamp = db.Column(db.DateTime, default=datetime.utcnow, index=True)
